﻿using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Threading;
using FlitBit.Cache.CacheNet;
using FlitBit.Core;

namespace FlitBit.Cache
{
	/// <summary>
	///   Default eviction manager implementation.
	/// </summary>
	public class DefaultCacheEvictionManager : Disposable, ICacheEvictionManager
	{
		static readonly int DefaultMaxConcurrentEvictionWorkers = Math.Max(Environment.ProcessorCount / 2, 1);

		readonly ConcurrentQueue<Func<bool, bool>> _callbacks = new ConcurrentQueue<Func<bool, bool>>();
		readonly int _maxConcurrentEvictionWorkers;

		readonly ConcurrentDictionary<Guid, MonitorRegistration> _monitorRegistrations =
			new ConcurrentDictionary<Guid, MonitorRegistration>();

		readonly ConcurrentDictionary<string, ConcurrentBag<MonitorRegistration>> _monitors =
			new ConcurrentDictionary<string, ConcurrentBag<MonitorRegistration>>();

		int _evictionWorkers;
		int _evictionWorkersExecuting;
		string _what;

		/// <summary>
		///   Creates a new instance.
		/// </summary>
		public DefaultCacheEvictionManager()
			: this(DefaultMaxConcurrentEvictionWorkers)
		{}

		/// <summary>
		///   Creates a new instance.
		/// </summary>
		/// <param name="maxConcurrentEvictionWorkers"></param>
		public DefaultCacheEvictionManager(int maxConcurrentEvictionWorkers)
		{
			_maxConcurrentEvictionWorkers = maxConcurrentEvictionWorkers;
		}

		string What
		{
			get
			{
				return Util.NonBlockingLazyInitializeVolatile(ref _what, () => this.GetType()
																																					.GetReadableFullName());
			}
		}

		/// <summary>
		///   Notifies caches of network cache evictions.
		/// </summary>
		/// <param name="message"></param>
		protected virtual void OnEvictionNotification(ICacheNetMessage message)
		{}

		/// <summary>
		///   Performs the instance's disposal.
		/// </summary>
		/// <param name="disposing"></param>
		/// <returns></returns>
		protected override bool PerformDispose(bool disposing)
		{
			return disposing;
		}

		internal void NotifyCacheNetMessage(ICacheNetMessage message)
		{}

		void Background_ProcessEvictions(object state)
		{
			try
			{
				if (LogSink.ShouldTrace(this, TraceEventType.Verbose))
				{
					LogSink.OnTraceEvent(this, TraceEventType.Verbose,
															String.Concat(this.What, ".Background_ProcessEvictions: worker started"));
				}
				Interlocked.Increment(ref _evictionWorkersExecuting);
				Func<bool, bool> callback;
				while (_callbacks.TryDequeue(out callback))
				{
					if (callback(false))
					{
						_callbacks.Enqueue(callback);
					}
				}
			}
			finally
			{
				Interlocked.Decrement(ref _evictionWorkers);
				Interlocked.Decrement(ref _evictionWorkersExecuting);
				//__evictionWorkers.Decrement();
				if (LogSink.ShouldTrace(this, TraceEventType.Verbose))
				{
					LogSink.OnTraceEvent(this, TraceEventType.Verbose,
															String.Concat(this.What, ".Background_ProcessEvictions: worker done"));
				}
			}
		}

		#region ICacheEvictionManager Members

		/// <summary>
		///   Adds an eviction monitor.
		/// </summary>
		/// <param name="monitor">the monitor</param>
		public void AddEvictionMonitor(ICacheEvictionMonitor monitor)
		{
			Contract.Assert(monitor != null);

			var key = monitor.RegistrationKey;
			var reg = new MonitorRegistration
			{
				Monitor = monitor
			};
			if (_monitorRegistrations.TryAdd(key, reg))
			{
				foreach (var ctx in monitor.ContextKeys)
				{
					var bag = _monitors.GetOrAdd(ctx, c => new ConcurrentBag<MonitorRegistration>());
					bag.Add(reg);
				}
			}
		}

		/// <summary>
		///   Removes an eviction monitor.
		/// </summary>
		/// <param name="key">the monitor's key</param>
		public void RemoveEvictionMonitor(Guid key)
		{
			MonitorRegistration reg;
			if (_monitorRegistrations.TryRemove(key, out reg))
			{
				reg.Removed = true;
			}
		}

		/// <summary>
		///   Adds an eviction callback to be run on then next
		///   eviction cycle.
		/// </summary>
		/// <param name="callback"></param>
		public void AddEvictionCallback(Func<bool, bool> callback)
		{
			Contract.Assert(callback != null);

			_callbacks.Enqueue(callback);
			if (Thread.VolatileRead(ref _evictionWorkers) < _maxConcurrentEvictionWorkers)
			{
				ThreadPool.QueueUserWorkItem(Background_ProcessEvictions, null);
				Interlocked.Increment(ref _evictionWorkers);
				//__evictionWorkers.Increment();
			}
		}

		#endregion

		private class MonitorRegistration
		{
			internal ICacheEvictionMonitor Monitor { get; set; }
			internal bool Removed { get; set; }
		}
	}
}